#pragma once

#include "Configuration/UnityConfigure.h"
#include "Runtime/Utilities/dynamic_array.h"
#include "Texture.h"
#include "External/Allegorithmic/builds/Engines/include/substance/handle.h"

class ColorRGBAf;
class ColorRGBA32;
class ProceduralMaterial;

enum ProceduralOutputType
{
	Substance_OType_Unknown = 0,
	Substance_OType_Diffuse,
	Substance_OType_Normal,
	Substance_OType_Height,
	Substance_OType_Emissive,
	Substance_OType_Specular,
	Substance_OType_Opacity,
	ProceduralOutputType_Count
};

enum SubstanceOutputFormat 
{
	Substance_OFormat_Compressed = 0,
	Substance_OFormat_Raw
};

/* A ProceduralTexture is a dynamic texture generated by the Substance engine.
 * It's part of a Substance graph hierarchy, compound of ProceduralMaterial, SubstanceArchive.
 */


class ProceduralTexture : public Texture
{
public:

	// Uploaded texture parameters
	struct TextureParameters
	{
		DECLARE_SERIALIZE(TextureParameters)
	
		int                     width;
		int                     height;
		int                     mipLevels;
		TextureFormat           textureFormat;

		bool IsValid () const { return width != 0; }
		friend bool operator == (const TextureParameters& lhs, const TextureParameters& rhs)
		{
			return lhs.width == rhs.width && lhs.height == rhs.height && lhs.mipLevels == rhs.mipLevels && lhs.textureFormat == rhs.textureFormat;
		}
		
		TextureParameters ();
		TextureParameters (int inWidth, int inHeight, int inMipLevels, TextureFormat inFormat);

		void Invalidate() { *this = TextureParameters(); }
	};

	// Flags
	enum Flag
	{
		Flag_Binded	= 1<<0,		// the output is altered by an input
		Flag_Uploaded = 1<<1,	// the data has been uploaded
        Flag_AwakeClone = 1<<2, // the texture need awake from clone
		Flag_Cached = 1<<3		// the texture has been cached, editor flag only
	};

	// Upload State
	enum UploadState
	{
		UploadState_None,		// no data has been uploaded
		UploadState_Waiting,	// blue waiting texture
		UploadState_Valid,		// lower states indicate the current uploaded data isn't valid
		UploadState_Baked,		// baked texture
		UploadState_Generated	// generated texture
	};

protected:	// FIELDS

	PPtr<ProceduralMaterial>	m_SubstanceMaterial;		// The parent procedural material from which we get generated
	ProceduralMaterial*			m_PingedMaterial;			// The pinged procedural material
	UInt64					    m_SubstanceTextureUID;		// The index of this texture (i.e. Substance output) WORD[shuffledID,baseID]
	unsigned int				m_SubstancePreShuffleUID;	// This ID is required to use substanceLinkerHandleSelectOutputs
	ProceduralOutputType		m_Type;
	ProceduralOutputType		m_AlphaSource;
	unsigned int			    m_Flags;
	UploadState					m_UploadState;
    SubstanceOutputFormat	    m_Format;
	TextureFormat				m_SubstanceFormat;			// Platform dependant format required for substance generation
	TextureParameters		    m_TextureParameters;
	std::vector<UInt8>		    m_BakedData;				// Baked texture if substance isn't supported
	TextureParameters		    m_BakedParameters;
    TextureID                   m_ClonedID;					// Link to the source ID when cloned and not generated

public:		// METHODS

	REGISTER_DERIVED_CLASS( ProceduralTexture, Texture )
	DECLARE_OBJECT_SERIALIZE( ProceduralTexture )
	
	ProceduralTexture( MemLabelId label, ObjectCreationMode mode );
    
#if ENABLE_SUBSTANCE    
    ProceduralTexture* Clone(ProceduralMaterial* owner);
    void AwakeClone();
#endif

	virtual int GetDataWidth() const { return m_TextureParameters.width; }
	virtual int GetDataHeight() const { return m_TextureParameters.height; }
	TextureFormat GetDataFormat() const { return m_TextureParameters.textureFormat; }
	int GetDataMipLevels() const { return m_TextureParameters.mipLevels; }
	virtual TextureDimension GetDimension () const { return kTexDim2D; }
	virtual bool HasMipMap () const { return m_TextureParameters.mipLevels != 1; }
	virtual int CountMipmaps() const { return m_TextureParameters.mipLevels; }
	
	const ProceduralOutputType& GetType() const { return m_Type; }
	const ProceduralOutputType& GetAlphaSource() const { return m_AlphaSource; }
	bool IsFlagEnabled(const Flag& flag) const { return m_Flags & (unsigned int)flag; }
	void EnableFlag(const Flag& flag, bool enabled=true) { if (enabled) m_Flags |= (unsigned int)flag; else m_Flags &= ~(unsigned int)flag; }

#if ENABLE_PROFILER
	virtual int GetStorageMemorySize() const { return m_TextureParameters.width * m_TextureParameters.height; }
#endif
	virtual bool ExtractImage (ImageReference* image, int imageIndex = 0) const { return false; }

	virtual void UnloadFromGfxDevice(bool forceUnloadAll);
	virtual void UploadToGfxDevice();
	void RemoveTexture ();
	void UploadWaitingTexture ();
	void UploadBakedTexture ();

	// Gets the output texture UID (i.e. output UID for the Substance engine)
	unsigned int GetSubstanceTextureUID() const	{ return m_SubstanceTextureUID & 0xffffffff; }
	unsigned int GetSubstanceBaseTextureUID() const	{ return (m_SubstanceTextureUID >> 32); }
	
	void SetSubstancePreShuffleUID(unsigned int id) { m_SubstancePreShuffleUID = id; }
	unsigned int GetSubstancePreShuffleUID() const { return m_SubstancePreShuffleUID; }

	// Set the UIDs
	// (Don't call externally though !)
	void SetSubstanceShuffledUID(unsigned int textureUID);
	void SetSubstanceFormat(TextureFormat format) { m_SubstanceFormat = format; }	
	TextureFormat GetSubstanceFormat() const { return m_SubstanceFormat; }

	virtual void AwakeFromLoadThreaded();
	virtual void AwakeFromLoad( AwakeFromLoadMode awakeMode );

	virtual bool ShouldIgnoreInGarbageDependencyTracking () { return false; }

	// (Don't call externally though !)
	void UploadSubstanceTexture(SubstanceTexture& outputTexture);

	bool HasBeenGenerated() const { return m_UploadState==UploadState_Generated; }
	bool HasBeenUploaded() const { return (m_UploadState==UploadState_Generated) || (m_UploadState==UploadState_Baked); }
	
#if UNITY_EDITOR
	// Creates the texture (one time call only by importer)
	void Init( ProceduralMaterial& _Parent, int _TextureIndex, ProceduralOutputType type, SubstanceOutputFormat format, ProceduralOutputType alphaSource, bool requireCompressed );
	const TextureParameters& GetTextureParameters() const { return m_TextureParameters; }
	TextureParameters& GetTextureParameters() { return m_TextureParameters; }
	
	virtual TextureFormat GetEditorUITextureFormat () const { return m_TextureParameters.textureFormat; }
#endif
	
	bool IsBaked() const;
	std::vector<UInt8>& GetBakedData() { return m_BakedData; }
	const TextureParameters& GetBakedParameters() const { return m_BakedParameters; }
	TextureParameters& GetBakedParameters() { return m_BakedParameters; }

	bool GetPixels32(int x, int y, int width, int height, ColorRGBA32* data);

	bool IsValid() { return m_TextureParameters.IsValid(); }
	void Invalidate();

	void SetOwner(ProceduralMaterial* material);
	ProceduralMaterial* GetSubstanceMaterial() { return m_PingedMaterial; }
#if UNITY_EDITOR
	PPtr<ProceduralMaterial>& GetSubstanceMaterialPtr() { return m_SubstanceMaterial; }
#endif
};

template<class T>
void ProceduralTexture::TextureParameters::Transfer(T& transfer)
{
	TRANSFER(width);
	TRANSFER(height);
	TRANSFER(mipLevels);
	transfer.Transfer(reinterpret_cast<int&> (textureFormat), "textureFormat");
}
